package com.bowlong.bio3;

import static com.bowlong.bio3.B3Types.BYTES;
import static com.bowlong.bio3.B3Types.BYTES_0;
import static com.bowlong.bio3.B3Types.DATE;
import static com.bowlong.bio3.B3Types.INT_ARRAY;
import static com.bowlong.bio3.B3Types.INT_ARRAY_0;
import static com.bowlong.bio3.B3Types.NULL;
import static com.bowlong.bio3.B3Types.STR_ARRAY;
import static com.bowlong.bio3.B3Types.STR_ARRAY_0;
import static com.bowlong.bio3.B3Constants.*;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class B3InputStream implements B3Input {

	int ptr;
	final InputStream in;

	@Override
	public int ptr() {
		return 0;
	}

	public B3InputStream(InputStream in) {
		this.in = in;
	}

	@Override
	public void readFully(byte[] b) throws IOException {
		in.read(b);
		ptr += b.length;
	}

	public static void readFully(InputStream in, byte[] b) throws IOException {
		in.read(b);
	}

	@Override
	public void readFully(byte[] b, int off, int len) throws IOException {
		in.read(b, off, len);
		ptr += len;
	}

	public static void readFully(InputStream in, byte[] b, int off, int len)
			throws IOException {
		in.read(b, off, len);
	}

	public int read() throws IOException {
		ptr++;
		return in.read();
	}

	public static int read(InputStream in) throws IOException {
		return in.read();
	}

	public B3Types readTag() throws IOException {
		return readTag(in);
	}

	public static B3Types readTag(InputStream in) throws IOException {
		int ordinal = read(in);
		if (ordinal < 0) {
			System.out.println("readTag:" + ordinal);
			throw new EOFException();
		}
		return B3Types.values()[ordinal];
	}

	@Override
	public int skipBytes(int n) throws IOException {
		return (int) in.skip(n);
	}

	public static int skipBytes(InputStream in, int n) throws IOException {
		return (int) in.skip(n);
	}

	@Override
	public boolean readBoolean() throws IOException {
		return readBoolean(in);
	}

	public static boolean readBoolean(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readBoolean(in, tag);
	}

	public boolean readBoolean(B3Types tag) throws IOException {
		return readBoolean(in, tag);
	}

	public static boolean readBoolean(InputStream in, B3Types tag)
			throws IOException {
		switch (tag) {
		case BOOL_TRUE:
			return true;
		case BOOL_FALSE:
			return false;
		default:
			throw new IOException("unknow type.");
		}
	}

	@Override
	public byte readByte() throws IOException {
		return readByte(in);
	}

	public static byte readByte(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readByte(in, tag);
	}

	public byte readByte(B3Types tag) throws IOException {
		return readByte(in, tag);
	}

	public static byte readByte(InputStream in, B3Types tag) throws IOException {
		switch (tag) {
		case BYTE_0:
			return 0;
		case BYTE:
			return (byte) read(in);
		default:
			throw new IOException("unknow type.");
		}
	}

	@Override
	public int readUnsignedByte() throws IOException {
		return readUnsignedByte(in);
	}

	public static int readUnsignedByte(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readUnsignedByte(in, tag);
	}

	public int readUnsignedByte(B3Types tag) throws IOException {
		return readUnsignedByte(in, tag);
	}

	public static int readUnsignedByte(InputStream in, B3Types tag)
			throws IOException {
		switch (tag) {
		case BYTE_0:
			return 0;
		case BYTE:
			return (int) (read(in) & 0xFF);
		default:
			throw new IOException("unknow type.");
		}
	}

	@Override
	public short readShort() throws IOException {
		return readShort(in);
	}

	public static short readShort(InputStream in) throws IOException {
		return (short) readInt(in);
	}

	@Override
	public int readUnsignedShort() throws IOException {
		return readUnsignedShort(in);
	}

	public static int readUnsignedShort(InputStream in) throws IOException {
		return readInt(in);
	}

	@Override
	public char readChar() throws IOException {
		throw new IOException("unknow type.");
	}

	@Override
	public int readInt() throws IOException {
		return readInt(in);
	}

	public static int readInt(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readInt(in, tag);
	}

	public int readInt(B3Types tag) throws IOException {
		return readInt(in, tag);
	}

	public static int readInt(InputStream in, B3Types tag) throws IOException {
		switch (tag) {
		case INT_N1:
			return -1;
		case INT_0:
			return 0;
		case INT_1:
			return 1;
		case INT_2:
			return 2;
		case INT_3:
			return 3;
		case INT_4:
			return 4;
		case INT_5:
			return 5;
		case INT_6:
			return 6;
		case INT_7:
			return 7;
		case INT_8:
			return 8;
		case INT_9:
			return 9;
		case INT_10:
			return 10;
		case INT_11:
			return 11;
		case INT_12:
			return 12;
		case INT_13:
			return 13;
		case INT_14:
			return 14;
		case INT_15:
			return 15;
		case INT_16:
			return 16;
		case INT_17:
			return 17;
		case INT_18:
			return 18;
		case INT_19:
			return 19;
		case INT_20:
			return 20;
		case INT_21:
			return 21;
		case INT_22:
			return 22;
		case INT_23:
			return 23;
		case INT_24:
			return 24;
		case INT_25:
			return 25;
		case INT_26:
			return 26;
		case INT_27:
			return 27;
		case INT_28:
			return 28;
		case INT_29:
			return 29;
		case INT_30:
			return 30;
		case INT_31:
			return 31;
		case INT_32:
			return 32;
		case INT_8B: {
			int v = read(in);
			return (byte) v;
		}
		case INT_16B: {
			int value1 = read(in);
			int value2 = read(in);

			int v = (((value1 & 0xff) << 8) + ((value2 & 0xff) << 0));
			return (short) v;
		}
		case INT_32B: {
			int value1 = read(in);
			int value2 = read(in);
			int value3 = read(in);
			int value4 = read(in);

			int v = ((value1 & 0xff) << 24) + ((value2 & 0xff) << 16)
					+ ((value3 & 0xff) << 8) + ((value4 & 0xff) << 0);
			return v;
		}
		default:
			throw new IOException("unknow type.");
		}
	}

	@Override
	public long readLong() throws IOException {
		return readLong(in);
	}

	public static long readLong(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readLong(in, tag);
	}

	public long readLong(B3Types tag) throws IOException {
		return readLong(in, tag);
	}

	public static long readLong(InputStream in, B3Types tag) throws IOException {
		switch (tag) {
		case LONG_0:
			return 0;
		case LONG_8B: {
			long v = read(in);
			return (byte) v;
		}
		case LONG_16B: {
			int v0 = read(in);
			int v1 = read(in);
			long v = (((v0 & 0xff) << 8) + ((v1 & 0xff) << 0));
			return (short) v;
		}
		case LONG_32B: {
			int v0 = read(in);
			int v1 = read(in);
			int v2 = read(in);
			int v3 = read(in);
			long v = ((v0 & 0xff) << 24) + ((v1 & 0xff) << 16)
					+ ((v2 & 0xff) << 8) + ((v3 & 0xff) << 0);
			return (int) v;
		}
		case LONG_64B: {
			int v0 = read(in);
			int v1 = read(in);
			int v2 = read(in);
			int v3 = read(in);
			int v4 = read(in);
			int v5 = read(in);
			int v6 = read(in);
			int v7 = read(in);
			long high = ((v0 & 0xff) << 24) + ((v1 & 0xff) << 16)
					+ ((v2 & 0xff) << 8) + ((v3 & 0xff) << 0);
			long low = ((v4 & 0xff) << 24) + ((v5 & 0xff) << 16)
					+ ((v6 & 0xff) << 8) + ((v7 & 0xff) << 0);
			long v = (high << 32) + (0xffffffffL & low);
			return v;
		}
		default:
			throw new IOException("unknow type.");
		}
	}

	@Override
	public float readFloat() throws IOException {
		return readFloat(in);
	}

	public static float readFloat(InputStream in) throws IOException {
		return (float) readDouble(in);
	}

	@Override
	public double readDouble() throws IOException {
		return readDouble(in);
	}

	public static double readDouble(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readDouble(in, tag);
	}

	public double readDouble(B3Types tag) throws IOException {
		return readDouble(in, tag);
	}

	public static double readDouble(InputStream in, B3Types tag)
			throws IOException {
		switch (tag) {
		case DOUBLE_0:
			return 0;
		case DOUBLE_64:
			int v0 = read(in);
			int v1 = read(in);
			int v2 = read(in);
			int v3 = read(in);
			int v4 = read(in);
			int v5 = read(in);
			int v6 = read(in);
			int v7 = read(in);
			long high = ((v0 & 0xff) << 24) + ((v1 & 0xff) << 16)
					+ ((v2 & 0xff) << 8) + ((v3 & 0xff) << 0);
			long low = ((v4 & 0xff) << 24) + ((v5 & 0xff) << 16)
					+ ((v6 & 0xff) << 8) + ((v7 & 0xff) << 0);
			long v = (high << 32) + (0xffffffffL & low);
			double ret = Double.longBitsToDouble(v);
			return new Double(ret);
		default:
			throw new IOException("unknow type.");
		}
	}

	@Override
	public String readLine() throws IOException {
		throw new IOException("un support method.");
	}

	@Override
	public String readUTF() throws IOException {
		return readUTF(in);
	}

	public static String readUTF(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readUTF(in, tag);
	}

	public String readUTF(B3Types tag) throws IOException {
		return readUTF(in, tag);
	}

	public static String readUTF(InputStream in, B3Types tag)
			throws IOException {
		if (tag == NULL)
			return null;

		int size = 0;
		switch (tag) {
		case UTF8_0:
			return "";
		case UTF8_1: {
			size = 1;
			break;
		}
		case UTF8_2: {
			size = 2;
			break;
		}
		case UTF8_3: {
			size = 3;
			break;
		}
		case UTF8_4: {
			size = 4;
			break;
		}
		case UTF8_5: {
			size = 5;
			break;
		}
		case UTF8_6: {
			size = 6;
			break;
		}
		case UTF8_7: {
			size = 7;
			break;
		}
		case UTF8_8: {
			size = 8;
			break;
		}
		case UTF8_9: {
			size = 9;
			break;
		}
		case UTF8_10: {
			size = 10;
			break;
		}
		case UTF8_11: {
			size = 11;
			break;
		}
		case UTF8_12: {
			size = 12;
			break;
		}
		case UTF8_13: {
			size = 13;
			break;
		}
		case UTF8_14: {
			size = 14;
			break;
		}
		case UTF8_15: {
			size = 15;
			break;
		}
		case UTF8_16: {
			size = 16;
			break;
		}
		case UTF8_17: {
			size = 17;
			break;
		}
		case UTF8_18: {
			size = 18;
			break;
		}
		case UTF8_19: {
			size = 19;
			break;
		}
		case UTF8_20: {
			size = 20;
			break;
		}
		case UTF8_21: {
			size = 21;
			break;
		}
		case UTF8_22: {
			size = 22;
			break;
		}
		case UTF8_23: {
			size = 23;
			break;
		}
		case UTF8_24: {
			size = 24;
			break;
		}
		case UTF8_25: {
			size = 25;
			break;
		}
		case UTF8_26: {
			size = 26;
			break;
		}
		case UTF8_27: {
			size = 27;
			break;
		}
		case UTF8_28: {
			size = 28;
			break;
		}
		case UTF8_29: {
			size = 29;
			break;
		}
		case UTF8_30: {
			size = 30;
			break;
		}
		case UTF8_31: {
			size = 31;
			break;
		}
		case UTF8_32: {
			size = 32;
			break;
		}
		case UTF8: {
			size = readInt(in);
			break;
		}
		default: {
			throw new IOException("unknow type.");
		}
		}
		byte[] b = new byte[size];
		readFully(in, b);
		return new String(b, UTF_8);
	}

	@Override
	public Date readDate() throws IOException {
		return readDate(in);
	}

	public static Date readDate(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readDate(in, tag);
	}

	public Date readDate(B3Types tag) throws IOException {
		return readDate(in, tag);
	}

	public static Date readDate(InputStream in, B3Types tag) throws IOException {
		if (tag == NULL)
			return null;
		else if (tag == DATE) {
			int v0 = read(in);
			int v1 = read(in);
			int v2 = read(in);
			int v3 = read(in);
			int v4 = read(in);
			int v5 = read(in);
			int v6 = read(in);
			int v7 = read(in);
			long high = ((v0 & 0xff) << 24) + ((v1 & 0xff) << 16)
					+ ((v2 & 0xff) << 8) + ((v3 & 0xff) << 0);
			long low = ((v4 & 0xff) << 24) + ((v5 & 0xff) << 16)
					+ ((v6 & 0xff) << 8) + ((v7 & 0xff) << 0);
			long v = (high << 32) + (0xffffffffL & low);

			return new Date(v);
		} else {
			throw new IOException("unknow type.");
		}
	}

	@Override
	public List<Object> readList() throws IOException {
		return readList(in);
	}

	public static List<Object> readList(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readList(in, tag);
	}

	public List<Object> readList(B3Types tag) throws IOException {
		return readList(in, tag);
	}

	public static List<Object> readList(InputStream in, B3Types tag)
			throws IOException {
		if (tag == NULL)
			return null;
		int size = 0;
		switch (tag) {
		case LIST_0:
			return new ArrayList<>();
		case LIST_1: {
			size = 1;
			break;
		}
		case LIST_2: {
			size = 2;
			break;
		}
		case LIST_3: {
			size = 3;
			break;
		}
		case LIST_4: {
			size = 4;
			break;
		}
		case LIST_5: {
			size = 5;
			break;
		}
		case LIST_6: {
			size = 6;
			break;
		}
		case LIST_7: {
			size = 7;
			break;
		}
		case LIST_8: {
			size = 8;
			break;
		}
		case LIST_9: {
			size = 9;
			break;
		}
		case LIST_10: {
			size = 10;
			break;
		}
		case LIST_11: {
			size = 11;
			break;
		}
		case LIST_12: {
			size = 12;
			break;
		}
		case LIST_13: {
			size = 13;
			break;
		}
		case LIST_14: {
			size = 14;
			break;
		}
		case LIST_15: {
			size = 15;
			break;
		}
		case LIST_16: {
			size = 16;
			break;
		}
		case LIST: {
			size = readInt(in);
			break;
		}
		default:
			throw new IOException("unknow type.");
		}

		List<Object> list = new ArrayList<>(size);
		for (int i = 0; i < size; i++) {
			Object e = readObject(in);
			list.add(e);
		}
		return list;
	}

	@Override
	public Set<Object> readSet() throws IOException {
		return readSet(in);
	}

	public static Set<Object> readSet(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readSet(in, tag);
	}

	public Set<Object> readSet(B3Types tag) throws IOException {
		return readSet(in, tag);
	}

	public static Set<Object> readSet(InputStream in, B3Types tag)
			throws IOException {
		if (tag == NULL)
			return null;

		int size = 0;
		switch (tag) {
		case SET_0:
			return new HashSet<>();
		case SET:
			size = readInt(in);
			break;
		default:
			throw new IOException("unknow type.");
		}

		Set<Object> set = new HashSet<>();
		for (int i = 0; i < size; i++) {
			Object e = readObject(in);
			set.add(e);
		}
		return set;
	}

	@Override
	public Map<Object, Object> readMap() throws IOException {
		return readMap(in);
	}

	public static Map<Object, Object> readMap(InputStream in)
			throws IOException {
		B3Types tag = readTag(in);
		return readMap(in, tag);
	}

	public Map<Object, Object> readMap(B3Types tag) throws IOException {
		return readMap(in, tag);
	}

	public static Map<Object, Object> readMap(InputStream in, B3Types tag)
			throws IOException {
		if (tag == NULL)
			return null;

		int size = 0;
		switch (tag) {
		case MAP_0:
			return new HashMap<>();
		case MAP_1: {
			size = 1;
			break;
		}
		case MAP_2: {
			size = 2;
			break;
		}
		case MAP_3: {
			size = 3;
			break;
		}
		case MAP_4: {
			size = 4;
			break;
		}
		case MAP_5: {
			size = 5;
			break;
		}
		case MAP_6: {
			size = 6;
			break;
		}
		case MAP_7: {
			size = 7;
			break;
		}
		case MAP_8: {
			size = 8;
			break;
		}
		case MAP_9: {
			size = 9;
			break;
		}
		case MAP_10: {
			size = 10;
			break;
		}
		case MAP_11: {
			size = 11;
			break;
		}
		case MAP_12: {
			size = 12;
			break;
		}
		case MAP_13: {
			size = 13;
			break;
		}
		case MAP_14: {
			size = 14;
			break;
		}
		case MAP_15: {
			size = 15;
			break;
		}
		case MAP_16: {
			size = 16;
			break;
		}
		case MAP: {
			size = readInt(in);
			break;
		}
		default:
			throw new IOException("unknow type.");
		}
		Map<Object, Object> map = new HashMap<>(size);
		for (int i = 0; i < size; i++) {
			Object key = readObject(in);
			Object value = readObject(in);
			map.put(key, value);
		}
		return map;
	}

	@Override
	public byte[] readBytes() throws IOException {
		return readBytes(in);
	}

	public static byte[] readBytes(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readBytes(in, tag);
	}

	public byte[] readBytes(B3Types tag) throws IOException {
		return readBytes(in, tag);
	}

	public static byte[] readBytes(InputStream in, B3Types tag)
			throws IOException {
		if (tag == NULL)
			return null;
		else if (tag == BYTES_0)
			return new byte[0];
		else if (tag == BYTES) {
			int size = readInt(in);
			byte[] b = new byte[size];
			readFully(in, b);
			return b;
		} else
			throw new IOException("unknow type.");
	}

	@Override
	public int[] readIntArray() throws IOException {
		return readIntArray(in);
	}

	public static int[] readIntArray(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readIntArray(in, tag);
	}

	public int[] readIntArray(B3Types tag) throws IOException {
		return readIntArray(in, tag);
	}

	public static int[] readIntArray(InputStream in, B3Types tag)
			throws IOException {
		if (tag == NULL)
			return null;
		if (tag == INT_ARRAY_0)
			return new int[0];
		else if (tag == INT_ARRAY) {
			int size = readInt(in);
			int[] array = new int[size];
			for (int i = 0; i < size; i++) {
				array[i] = readInt(in);
			}
			return array;
		} else {
			throw new IOException("unknow type.");
		}
	}

	@Override
	public String[] readStrArray() throws IOException {
		return readStrArray(in);
	}

	public static String[] readStrArray(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readStrArray(in, tag);
	}

	public String[] readStrArray(B3Types tag) throws IOException {
		return readStrArray(in, tag);
	}

	public static String[] readStrArray(InputStream in, B3Types tag)
			throws IOException {
		if (tag == NULL)
			return null;
		else if (tag == STR_ARRAY_0)
			return new String[0];
		else if (tag == STR_ARRAY) {
			int size = readInt(in);
			String[] array = new String[size];
			for (int i = 0; i < size; i++) {
				array[i] = readUTF(in);
			}
			return array;
		} else
			throw new IOException("unknow type.");
	}

	@Override
	public Object readObject() throws IOException {
		return readObject(in);
	}

	public static Object readObject(InputStream in) throws IOException {
		B3Types tag = readTag(in);
		return readObject(in, tag);
	}

	public Object readObject(B3Types tag) throws IOException {
		return readObject(in, tag);
	}

	public static Object readObject(InputStream in, B3Types tag)
			throws IOException {
		if (tag == NULL)
			return null;

		switch (tag) {
		// boolean
		case BOOL_TRUE:
		case BOOL_FALSE:
			return readBoolean(in, tag);
			// byte
		case BYTE_0:
		case BYTE:
			return readByte(in, tag);
			// int
		case INT_N1:
		case INT_0:
		case INT_1:
		case INT_2:
		case INT_3:
		case INT_4:
		case INT_5:
		case INT_6:
		case INT_7:
		case INT_8:
		case INT_9:
		case INT_10:
		case INT_11:
		case INT_12:
		case INT_13:
		case INT_14:
		case INT_15:
		case INT_16:
		case INT_17:
		case INT_18:
		case INT_19:
		case INT_20:
		case INT_21:
		case INT_22:
		case INT_23:
		case INT_24:
		case INT_25:
		case INT_26:
		case INT_27:
		case INT_28:
		case INT_29:
		case INT_30:
		case INT_31:
		case INT_32:
		case INT_8B:
		case INT_16B:
		case INT_32B:
			return readInt(in, tag);
			// long
		case LONG_0:
		case LONG_8B:
		case LONG_16B:
		case LONG_32B:
		case LONG_64B:
			return readLong(in, tag);
			// double
		case DOUBLE_0:
		case DOUBLE_64:
			return readDouble(in, tag);
			// String
		case UTF8_0:
		case UTF8_1:
		case UTF8_2:
		case UTF8_3:
		case UTF8_4:
		case UTF8_5:
		case UTF8_6:
		case UTF8_7:
		case UTF8_8:
		case UTF8_9:
		case UTF8_10:
		case UTF8_11:
		case UTF8_12:
		case UTF8_13:
		case UTF8_14:
		case UTF8_15:
		case UTF8_16:
		case UTF8_17:
		case UTF8_18:
		case UTF8_19:
		case UTF8_20:
		case UTF8_21:
		case UTF8_22:
		case UTF8_23:
		case UTF8_24:
		case UTF8_25:
		case UTF8_26:
		case UTF8_27:
		case UTF8_28:
		case UTF8_29:
		case UTF8_30:
		case UTF8_31:
		case UTF8_32:
		case UTF8:
			return readUTF(in, tag);
			// byte[]
		case BYTES_0:
		case BYTES:
			return readBytes(in, tag);
			// List
		case LIST_0:
		case LIST_1:
		case LIST_2:
		case LIST_3:
		case LIST_4:
		case LIST_5:
		case LIST_6:
		case LIST_7:
		case LIST_8:
		case LIST_9:
		case LIST_10:
		case LIST_11:
		case LIST_12:
		case LIST_13:
		case LIST_14:
		case LIST_15:
		case LIST_16:
		case LIST:
			return readList(in, tag);
			// Set
		case SET_0:
		case SET:
			return readSet(in, tag);
			// Map
		case MAP_0:
		case MAP_1:
		case MAP_2:
		case MAP_3:
		case MAP_4:
		case MAP_5:
		case MAP_6:
		case MAP_7:
		case MAP_8:
		case MAP_9:
		case MAP_10:
		case MAP_11:
		case MAP_12:
		case MAP_13:
		case MAP_14:
		case MAP_15:
		case MAP_16:
		case MAP:
			return readMap(in, tag);
			// Date
		case DATE:
			return readDate(in, tag);
			// int[]
		case INT_ARRAY_0:
		case INT_ARRAY:
			return readIntArray(in, tag);
			// String[]
		case STR_ARRAY_0:
		case STR_ARRAY:
			return readStrArray(in, tag);
		default:
			throw new IOException("unknow type.");
		}
	}

}
